package org.jflame.commons.util;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.ToDoubleFunction;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.stream.Collectors;

/**
 * 计算工具类
 * 
 * @author charles.zhang
 */
public class MathHelper {

    // 默认运算精度
    private static int DEF_SCALE = 10;
    public static final BigDecimal ONE_HUNDRED = new BigDecimal("100");

    /**
     * double加法。
     * 
     * @param num1 被加数
     * @param num2 加数
     * @return 两个参数的和
     */
    public static final double add(Number num1, Number num2) {
        BigDecimal result = NumberHelper.newDecimal(num1)
                .add(NumberHelper.newDecimal(num2));
        return result.setScale(DEF_SCALE, RoundingMode.HALF_UP)
                .doubleValue();
    }

    /**
     * double减法
     * 
     * @param num1 被减数
     * @param num2 减数
     * @return 差double
     */
    public static final double subtract(Number num1, Number num2) {
        BigDecimal result = NumberHelper.newDecimal(num1)
                .subtract(NumberHelper.newDecimal(num2));
        return result.setScale(DEF_SCALE, RoundingMode.HALF_UP)
                .doubleValue();
    }

    /**
     * float减法
     * 
     * @param num1
     * @param num2
     * @return 差 float
     */
    public static final float subtract(float num1, float num2) {
        BigDecimal result = NumberHelper.newDecimal(num1)
                .subtract(NumberHelper.newDecimal(num2));
        return result.setScale(DEF_SCALE, RoundingMode.HALF_UP)
                .floatValue();
    }

    /**
     * 乘法,默认精度10
     * 
     * @param num1 被乘数
     * @param num2 乘数
     * @return 积
     */
    public static final double multiply(Number num1, Number num2) {
        BigDecimal result = NumberHelper.newDecimal(num1)
                .multiply(NumberHelper.newDecimal(num2));
        return result.setScale(DEF_SCALE, RoundingMode.HALF_UP)
                .doubleValue();
    }

    /**
     * 乘法，返回double
     * 
     * @param num1 被乘数
     * @param num2 乘数
     * @param scale 精度
     * @return double积
     */
    public static final double multiply(Number num1, Number num2, int scale) {
        return multiplyDecimal(num1, num2, scale).doubleValue();
    }

    /**
     * 乘法，返回BigDecimal
     * 
     * @param num1 被乘数
     * @param num2 乘数
     * @param scale 精度
     * @return BigDecimal 积
     */
    public static final BigDecimal multiplyDecimal(Number num1, Number num2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("scale must be more then zero");
        }
        BigDecimal result = NumberHelper.newDecimal(num1)
                .multiply(NumberHelper.newDecimal(num2));
        return result.setScale(scale, RoundingMode.HALF_UP);
    }

    /**
     * 除法运算,默认精度10,超出精度以后的数字四舍五入.
     * 
     * @param num1 被除数
     * @param num2 除数
     * @return 商
     */
    public static final double divide(Number num1, Number num2) {
        return divide(num1, num2, DEF_SCALE);
    }

    /**
     * 除法运算,超出精度以后的数字四舍五入.返回double
     * 
     * @param num1 被除数
     * @param num2 除数
     * @param scale 精度
     * @return double 商
     */
    public static final double divide(Number num1, Number num2, int scale) {
        return divideDecimal(num1, num2, scale).doubleValue();
    }

    /**
     * 除法运算,超出精度以后的数字四舍五入，返回BigDecimal
     * 
     * @param num1 被除数
     * @param num2 除数
     * @param scale 精度
     * @return BigDecimal 商
     */
    public static final BigDecimal divideDecimal(Number num1, Number num2, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("scale must be more then zero");
        }
        BigDecimal dividend = NumberHelper.newDecimal(num1);
        BigDecimal divisor = NumberHelper.newDecimal(num2);
        if (BigDecimal.ZERO.compareTo(divisor) == 0) {
            return BigDecimal.ZERO;
        }
        return dividend.divide(divisor, scale, RoundingMode.HALF_UP);
    }

    /**
     * 提供精确的小数位四舍五入处理。
     * 
     * @param num 需要四舍五入的数字
     * @param scale 小数点后保留几位
     * @return 四舍五入后的结果
     */
    public static final double round(double num, int scale) {
        if (scale < 0) {
            throw new IllegalArgumentException("scale must be more then zero");
        }
        BigDecimal result = NumberHelper.newDecimal(num)
                .divide(BigDecimal.ONE, scale, RoundingMode.HALF_UP);
        return result.doubleValue();
    }

    /**
     * 计算百分比,除法保留2位小数。如:
     * <p>
     * 4除以5=80%, MathHelper.percent(new BigDecimal("4"),new BigDecimal("5"))=80
     * 
     * @param dividend 被除数
     * @param divisor 除数
     * @return
     */
    public static BigDecimal percent(BigDecimal dividend, BigDecimal divisor) {
        return percent(dividend, divisor, 2);
    }

    /**
     * 计算百分比。如:
     * <p>
     * 3除以10, MathHelper.percent(new BigDecimal("3"),new BigDecimal("10"),2)=33.33%
     * 
     * @param dividend 被除数
     * @param divisor 除数
     * @param scale 小数精度
     * @return
     */
    public static BigDecimal percent(BigDecimal dividend, BigDecimal divisor, int scale) {
        if (BigDecimal.ZERO.compareTo(dividend) == 0) {
            return BigDecimal.ZERO;
        }
        return divideDecimal(dividend, divisor, 2).movePointRight(2);
    }

    /**
     * 计算百分比,除法保留2位小数。如:
     * <p>
     * 4除以5=80%,percent(4d,5d)=80.0
     * 
     * @param dividend 被除数
     * @param divisor 除数
     * @return
     */
    public static double percent(double dividend, double divisor) {
        return percent(dividend, divisor, 2);
    }

    /**
     * 计算百分比。如:
     * <p>
     * 4除以5=80%,percent(4d,5d)=80.0
     * 
     * @param dividend 被除数
     * @param divisor 除数
     * @param scale 小数精度
     * @return
     */
    public static double percent(double dividend, double divisor, int scale) {
        if (dividend == 0) {
            return 0f;
        }

        return percent(NumberHelper.newDecimal(dividend), NumberHelper.newDecimal(divisor), scale).doubleValue();
    }

    /**
     * 计算百分比,除法保留2位小数。如:
     * <p>
     * 4除以5=80%,percent(4,5)=80.0
     * 
     * @param dividend 被除数
     * @param divisor 除数
     * @return
     */
    public static double percent(int dividend, int divisor) {
        return percent(dividend, divisor, 2);
    }

    /**
     * 计算百分比。如:
     * <p>
     * 4除以5=80%,percent(4,5)=80.0
     * 
     * @param dividend 被除数
     * @param divisor 除数
     * @param scale 小数精度
     * @return
     */
    public static double percent(int dividend, int divisor, int scale) {
        if (divisor == 0) {
            return 0d;
        }
        return percent(NumberHelper.newDecimal(dividend), NumberHelper.newDecimal(divisor), scale).doubleValue();
    }

    /**
     * 对Integer集合求和,空集合返回0. 注:'和'不应超过Integer.MAX_VALUE
     * 
     * @param ints
     * @return
     */
    public static int sumInt(Collection<Integer> ints) {
        if (CollectionHelper.isEmpty(ints)) {
            return 0;
        }
        return ints.stream()
                .collect(Collectors.summingInt(x -> x));
    }

    /**
     * 对多个int求和. 注:'和'不应超过Integer.MAX_VALUE
     *
     * @param ints
     * @return
     */
    public static int sumInt(int... ints) {
        if (ints.length == 0) {
            return 0;
        }
        return Arrays.stream(ints)
                .sum();
    }

    /**
     * 集合元素int类型属性累加. 示例:
     *
     * <pre>
     * {@code
     * CollectionHelper.sum(users, u -> u.getAge());
     * }
     * </pre>
     *
     * @param <T>
     * @param collection
     * @param mapper ToIntFunction 返回要累加的属性值
     * @return 返回元素和,如果集合为空返回0
     * @see #sum(Collection, ToDoubleFunction)
     * @see #sum(Collection, ToLongFunction)
     */
    public static <T> int sum(Collection<T> collection, ToIntFunction<T> mapper) {
        if (CollectionHelper.isEmpty(collection)) {
            return 0;
        }
        return collection.stream()
                .mapToInt(mapper)
                .sum();
    }

    /**
     * 对long集合求和,空集合返回0. 注:'和'不应超过Long.MAX_VALUE
     *
     * @param longs
     * @return
     */
    public static long sumLong(Collection<Long> longs) {
        if (CollectionHelper.isEmpty(longs)) {
            return 0;
        }
        return longs.stream()
                .collect(Collectors.summingLong(x -> x));
    }

    /**
     * 对多个long求和. 注:'和'不应超过Long.MAX_VALUE
     *
     * @param longs
     * @return
     */
    public static long sumLong(long... longs) {
        if (longs.length == 0) {
            return 0;
        }
        return Arrays.stream(longs)
                .sum();
    }

    /**
     * 集合元素long类型属性累加.
     *
     * @param <T>
     * @param collection
     * @param mapper
     * @return 返回元素和,如果集合为空返回0
     */
    public static <T> long sum(Collection<T> collection, ToLongFunction<T> mapper) {
        if (CollectionHelper.isEmpty(collection)) {
            return 0;
        }
        return collection.stream()
                .mapToLong(mapper)
                .sum();
    }

    /**
     * 对Double集合求和,空集合返回0. 注:'和'不应超过Double.MAX_VALUE
     * 
     * @param doubles
     * @return
     */
    public static double sumDouble(Collection<Double> doubles) {
        if (CollectionHelper.isEmpty(doubles)) {
            return 0;
        }
        return doubles.stream()
                .mapToDouble(x -> x)
                .sum();
    }

    /**
     * 对多个double求和. 注:'和'不应超过Double.MAX_VALUE
     *
     * @param doubles
     * @return
     */
    public static double sumDouble(double... doubles) {
        if (doubles.length == 0) {
            return 0;
        }
        return Arrays.stream(doubles)
                .sum();
    }

    /**
     * 集合元素double类型属性累加.
     *
     * @param <T>
     * @param collection
     * @param mapper
     * @return 返回元素和,如果集合为空返回0
     */
    public static <T> double sum(Collection<T> collection, ToDoubleFunction<T> mapper) {
        if (CollectionHelper.isEmpty(collection)) {
            return 0;
        }
        return collection.stream()
                .mapToDouble(mapper)
                .sum();
    }

    /**
     * 对BigDecimal集合求和,空集合返回BigDecimal.ZERO.
     * 
     * @param decimals
     * @return
     */
    public static BigDecimal sumDecimal(Collection<BigDecimal> decimals) {
        if (CollectionHelper.isEmpty(decimals)) {
            return BigDecimal.ZERO;
        }
        return decimals.stream()
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /**
     * 多个BigDecimal求和
     * 
     * @param decimals
     * @return
     */
    public static BigDecimal sumDecimal(BigDecimal... decimals) {
        if (ArrayHelper.isEmpty(decimals)) {
            return BigDecimal.ZERO;
        }

        return Arrays.stream(decimals)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /**
     * 集合元素BigDecimal类型属性累加.示例:
     *
     * <pre>
     * {@code
     *
     * List<Order> orders = newList(order1, order2);
     * BigDecimal totalAmount = CollectionHelper.sum(orders, o -> o.getAmount());
     *
     * }
     * </pre>
     *
     * @param <T> 集合元素类型
     * @param collection
     * @param mapper
     * @return BigDecimal 返回元素和,如果集合为空返回BigDecimal.ZERO
     * @see #sumDecimal(Collection)
     * @see #sumDecimal(BigDecimal...)
     */
    public static <T> BigDecimal sum(Collection<T> collection, Function<T,BigDecimal> mapper) {
        if (CollectionHelper.isEmpty(collection)) {
            return BigDecimal.ZERO;
        }
        return collection.stream()
                .map(mapper)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    /**
     * 人民币分转元,四舍五入保留两位小数
     * 
     * @param fen 分
     * @return BigDecimal 元
     */
    public static BigDecimal fenToYuan(BigDecimal fen) {
        return fen.movePointLeft(2)
                .setScale(2, RoundingMode.HALF_UP);
    }

    /**
     * 人民币分转元,四舍五入保留两位小数
     * 
     * @param fen 分
     * @return 元
     */
    public static BigDecimal fenToYuan(int fen) {
        return NumberHelper.newDecimal(fen)
                .movePointLeft(2)
                .setScale(2, RoundingMode.HALF_UP);
    }

    /**
     * 人民币元转分,四舍五入保留0位小数
     * 
     * @param yuan 元
     * @return BigDecimal 分
     */
    public static BigDecimal yuanToFen(BigDecimal yuan) {
        if (yuan == null || NumberHelper.isZero(yuan)) {
            return BigDecimal.ZERO;
        }
        return yuan.scaleByPowerOfTen(2)
                .setScale(0, RoundingMode.HALF_UP);
    }

    /**
     * 人民币元转分,四舍五入保留0位小数,结果转为int型
     * 
     * @param yuan 元
     * @return 分
     */
    public static int yuanToFenAsInt(BigDecimal yuan) {
        if (yuan == null || NumberHelper.isZero(yuan)) {
            return 0;
        }
        return yuan.scaleByPowerOfTen(2)
                .setScale(0, RoundingMode.HALF_UP)
                .intValue();
    }

}
